﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Co9320.NamaAlert.Engine
{
	internal enum LeapIndicator : byte
	{
		NoWarning = 0x00,
		Add = 0x01,
		Substract = 0x02,
		AlarmCondition = 0x03
	}

	internal enum ModeKind : byte
	{
		Reserved,
		SymmetricActive,
		SymemetricPassive,
		Client,
		Server,
		Broadcast,
		ReservedForNTP,
		ReservedPrivate
	}



	internal class NtpMessage
	{
		private static long TickToSecondMedia = 10000000L;

		public static byte[] CreateRequestMessage()
		{
			byte[] buff = new byte[48];
			buff[0] = 0x23;

			NtpTimeStamp t = new NtpTimeStamp(DateTime.Now.ToUniversalTime());
			byte[] stamp=t.ToBinary();

			for (int i = 40; i < 48; i++)
			{
				buff[i] = stamp[i - 40];
			}

			return buff;
		}

		public NtpMessage(byte[] Datas)
		{
			if (Datas.Length < 48)
			{
				throw new ArgumentException("Datas length is invalid");
			}


			byte tmp = Datas[0];

			SetLi(tmp);
			SetVN(tmp);
			SetMode(tmp);

			this.Statum = Datas[1];

			SetPoll(Datas[2]);
			this.Precision = (sbyte)Datas[3];


			SetRootDelay(Datas);
			SetRootDispersion(Datas);
			SetReferenceIdent(Datas);

			this.ReferenceTimestamp = new NtpTimeStamp(Datas, 16);
			this.OriginateTimeStamp = new NtpTimeStamp(Datas, 24);
			this.ReceiveTimestamp = new NtpTimeStamp(Datas, 32);
			this.TransmitTimestamp = new NtpTimeStamp(Datas, 40);
		}


		private void SetReferenceIdent(byte[] Datas)
		{
			Decoder ascii = Encoding.ASCII.GetDecoder();
			char[] buff = new char[4];

			ascii.GetChars(Datas, 12, 4, buff, 0);
			this.ID = new string(buff);
		}

		private void SetRootDispersion(byte[] Datas)
		{
			long ticks = GetTicks(Datas, 8);
			this.RootDispersion = new TimeSpan(ticks);
		}

		private void SetRootDelay(byte[] Datas)
		{
			long ticks = GetTicks(Datas, 4);
			this.RootDelay = new TimeSpan(ticks);
		}

		private static long GetTicks(byte[] Datas, int StartIndex)
		{
			long tmp = Datas[StartIndex];
			tmp <<= 8;
			tmp |= (long)Datas[StartIndex + 1];

			long ticks = tmp * 10000000;

			tmp = Datas[StartIndex + 2];
			tmp <<= 8;
			tmp |= (long)Datas[StartIndex + 3];



			int Digit = 7 - tmp.ToString("d").Length;
			Digit = (int)Math.Pow(10, Digit);
			tmp *= Digit;
			ticks += tmp;

			return ticks;
		}

		private void SetPoll(byte p)
		{
			double poll = Math.Pow(2, (double)p);
			poll *= TickToSecondMedia;

			if (poll > (double)long.MaxValue)
			{
				this.Pool = new TimeSpan(long.MaxValue);
			}
			else
			{
				this.Pool = new TimeSpan((long)poll);
			}
		}

		private void SetMode(byte Data)
		{
			byte tmp = (byte)(Data & 0xE0) ;
			tmp >>= 3;
			this.Mode = (ModeKind)tmp;
		}

		private void SetVN(byte Data)
		{
			byte tmp = (byte)(Data & 0x70);

			tmp >>= 3;

			this.Version = tmp;
		}

		private void SetLi(byte Data)
		{
			byte tmp = (byte)(Data & 0x03);
			this.LI = (LeapIndicator)tmp;
		}

		private NtpMessage()
		{
		}

		public LeapIndicator LI
		{
			get;
			private set;
		}

		public byte Version
		{
			get;
			private set;
		}


		public ModeKind Mode
		{
			get;
			private set;
		}

		public byte Statum
		{
			get;
			private set;
		}

		public TimeSpan Pool
		{
			get;
			private set;
		}

		public sbyte Precision
		{
			get;
			private set;
		}

		public TimeSpan RootDelay
		{
			get;
			private set;
		}

		public TimeSpan RootDispersion
		{
			get;
			private set;
		}

		public string ID
		{
			get;
			private set;
		}




		public NtpTimeStamp ReferenceTimestamp
		{
			get;
			private set;
		}

		public NtpTimeStamp OriginateTimeStamp
		{
			get;
			private set;
		}

		public NtpTimeStamp ReceiveTimestamp
		{
			get;
			private set;
		}

		public NtpTimeStamp TransmitTimestamp
		{
			get;
			private set;
		}
	}
}
